package com.yzk.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yzk.train.business.entity.*;
import com.yzk.train.business.enums.ConfirmOrderStatusEnum;
import com.yzk.train.business.enums.SeatColEnum;
import com.yzk.train.business.enums.SeatTypeEnum;
import com.yzk.train.business.mapper.ConfirmOrderMapper;
import com.yzk.train.business.request.ConfirmOrderDoReq;
import com.yzk.train.business.request.ConfirmOrderQueryReq;
import com.yzk.train.business.request.ConfirmOrderTicketReq;
import com.yzk.train.business.response.ConfirmOrderQueryResp;
import com.yzk.train.common.exception.BusinessException;
import com.yzk.train.common.exception.ExceptionEnum;
import com.yzk.train.common.response.PageResp;
import com.yzk.train.common.utils.SnowUtil;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class ConfirmOrderService {

    private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);

    @Resource
    private ConfirmOrderMapper confirmOrderMapper;

    @Resource
    private DailyTrainTicketService dailyTrainTicketService;

    @Resource
    private DailyTrainCarriageService dailyTrainCarriageService;

    @Resource
    private DailyTrainSeatService dailyTrainSeatService;

    @Resource
    private AfterConfirmOrderService afterConfirmOrderService;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 保存 / 更新 确认订单
     *
     * @param req
     */
    public String save(ConfirmOrderDoReq req) {
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
        DateTime now = DateTime.now();
        confirmOrder.setUpdateTime(now);
        if (ObjectUtil.isNull(confirmOrder.getId())) {
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrderMapper.insert(confirmOrder);
            return "保存成功";
        } else {
            confirmOrderMapper.updateByPrimaryKeySelective(confirmOrder);
            return "编辑成功";
        }
    }

    /**
     * 查询确认订单列表
     *
     * @param req
     */
    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.setOrderByClause("id desc");
        // 使用分页插件
        PageHelper.startPage(req.getPageNum(), req.getPageSize());
        List<ConfirmOrder> confirmOrders = confirmOrderMapper.selectByExample(confirmOrderExample);
        // 使用 PageInfo 包装查询结果
        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrders);
        // 转换为 PageResp
        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrders, ConfirmOrderQueryResp.class);
        return new PageResp<>(pageInfo.getTotal(), list);
    }

    /**
     * 根据ID删除确认订单
     *
     * @param id
     */
    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    /**
     * 更新订单状态
     *
     * @param confirmOrder
     * @param confirmOrderStatusEnum
     */
    public void updateConfirmOrderStatus(ConfirmOrder confirmOrder, ConfirmOrderStatusEnum confirmOrderStatusEnum) {
        ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
        confirmOrderForUpdate.setId(confirmOrder.getId());
        confirmOrderForUpdate.setUpdateTime(DateTime.now());
        confirmOrderForUpdate.setStatus(confirmOrderStatusEnum.getCode());
        confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
    }

    /**
     * 提交订单，确认订单
     *
     * @param req
     */
    public void doConfirm(ConfirmOrderDoReq req) {
        // 锁ID（乘车时间 + 乘车时间 + 车次编号）
        String lockKey = DateUtil.formatDate(req.getDate()) + "_" + req.getStartTime() + "_" + req.getTrainCode();
        RLock lock = null;
        try {
            // 获取 redisson分布式锁
            lock = redissonClient.getLock(lockKey);
            // 看门狗机制，无限期刷新锁过期时间，这里传0：拿不到锁就返回
            boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS);
            if (!tryLock) throw new BusinessException(ExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);

            while (true) {
                // 取确认订单表的记录，同日期车次，状态是I，分页处理，每次取N条
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
                criteria.andDateEqualTo(req.getDate())
                        .andTrainCodeEqualTo(req.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1, 5);
                List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);

                if (CollUtil.isEmpty(list)) {
                    LOG.info("没有需要处理的订单，结束循环");
                    break;
                } else {
                    LOG.info("本次处理{}条订单", list.size());
                }

                // 一条一条的卖
                list.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getE().equals(ExceptionEnum.TICKET_COUNT_NOT_ENOUGH_ERROR)) {
                            LOG.info("本订单余票不足，继续售卖下一个订单");
                            updateConfirmOrderStatus(confirmOrder, ConfirmOrderStatusEnum.EMPTY);
                        } else {
                            throw e;
                        }
                    }
                });
            }

        } catch (InterruptedException e) {
            LOG.info("锁异常");
        } finally {
            // 释放锁
            if (lock != null && lock.isHeldByCurrentThread()) lock.unlock();
        }

    }

    /**
     * 售票
     * @param confirmOrder confirmOrder
     */
    private void sell(ConfirmOrder confirmOrder) {
        // 为了演示排队效果，每次出票增加200毫秒延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 构造ConfirmOrderDoReq
        ConfirmOrderDoReq req = new ConfirmOrderDoReq();
        req.setMemberId(confirmOrder.getMemberId());
        req.setDate(confirmOrder.getDate());
        req.setTrainCode(confirmOrder.getTrainCode());
        req.setStart(confirmOrder.getStart());
        req.setEnd(confirmOrder.getEnd());
        req.setDailyTrainTicketId(confirmOrder.getDailyTrainTicketId());
        req.setTickets(JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class));
        req.setCaptchaCode("");
        req.setCaptchaToken("");
        req.setLogId("");

        // 省略业务数据校验，如：车次是否存在，余票是否存在，车次是否在有效期内，tickets条数>0，同乘客同车次是否已买过

        // 将订单设置成处理中，避免重复处理
        updateConfirmOrderStatus(confirmOrder, ConfirmOrderStatusEnum.PENDING);

        Date date = req.getDate();
        String trainCode = req.getTrainCode();
        String start = req.getStart();
        String end = req.getEnd();
        List<ConfirmOrderTicketReq> tickets = req.getTickets();

        // 查出余票记录，需要得到真实的库存
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);

        // 预扣减余票数量，并判断余票是否足够，余票不足将更新订单状态
        boolean isTicketEmpty = reduceTickets(req, dailyTrainTicket);
        if (isTicketEmpty) {
            updateConfirmOrderStatus(confirmOrder, ConfirmOrderStatusEnum.EMPTY);
            throw new BusinessException(ExceptionEnum.TICKET_COUNT_NOT_ENOUGH_ERROR);
        }

        // 最终的选座结果
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();

        // 计算相对第一个座位的偏移值
        // 比如选择的是C1,D2,则偏移值是：[0,5]
        // 比如选择的是A1,B1,C1,则偏移值是：[0,1,2]
        ConfirmOrderTicketReq ticketByOne = tickets.get(0);
        if (StrUtil.isNotBlank(ticketByOne.getSeat())) {
            LOG.info("本次购票有选座");
            // 查出本次选座的座位类型有哪些列，用于计算所选座位与第一个座位的偏离值
            List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketByOne.getSeatTypeCode());
            // 组成和前端选座一样的列表，用于作参照的座位列表；例：referSeatList = {A1, C1, D1, F1, A2, C2, D2, F2}
            List<String> referSeatList = new ArrayList<>();
            for (int i = 1; i < 3; i++) {
                for (SeatColEnum seatCol : colEnumList) {
                    referSeatList.add(seatCol.getCode() + i);
                }
            }
            LOG.info("用于参照的座位列表：{}", referSeatList);
            // 绝对偏移值，即：在参照列表中的位置
            ArrayList<Integer> absoluteOffset = new ArrayList<>();
            for (ConfirmOrderTicketReq ticketReq : tickets) {
                int index = referSeatList.indexOf(ticketReq.getSeat());
                absoluteOffset.add(index);
            }
            // 排序从小到大 解决前端参数顺序问题
            Collections.sort(absoluteOffset);
            LOG.info("绝对偏移值：{}", absoluteOffset);
            ArrayList<Integer> offsetList = new ArrayList<>();
            for (Integer index : absoluteOffset) {
                offsetList.add(index - absoluteOffset.get(0));
            }
            LOG.info("相对第一个座位的偏移值：{}", offsetList);
            // 开始选座
            selectSeat(
                    date,
                    trainCode,
                    ticketByOne.getSeatTypeCode(),
                    ticketByOne.getSeat().split("")[0],   // 挑选座位的列号，A1 = A
                    offsetList,
                    dailyTrainTicket.getStartIndex(),
                    dailyTrainTicket.getEndIndex(),
                    colEnumList.size(), // 座位类型列号数量，用于选座失败时，重新从下一个排开始选座
                    finalSeatList);
        } else {
            LOG.info("本次购票没有选座，系统自动为其选择合适的座位");
            for (ConfirmOrderTicketReq ticketReq : tickets) {
                selectSeat(
                        date,
                        trainCode,
                        ticketReq.getSeatTypeCode(),
                        null,
                        null,
                        dailyTrainTicket.getStartIndex(),
                        dailyTrainTicket.getEndIndex(),
                        null,
                        finalSeatList);
            }
        }

        // 最终选票数量没有达到预期购票数量，就是余票不足
        if (finalSeatList.size() != tickets.size()) {
            updateConfirmOrderStatus(confirmOrder, ConfirmOrderStatusEnum.EMPTY);
            throw new BusinessException(ExceptionEnum.TICKET_COUNT_NOT_ENOUGH_ERROR);
        }

        // 选中座位后事务处理
        try {
            afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, tickets, confirmOrder);
        } catch (Exception e) {
            LOG.error("出票失败：{}", e.getMessage());
            // 出现异常，将订单状态改为失败
            updateConfirmOrderStatus(confirmOrder, ConfirmOrderStatusEnum.FAILURE);
            throw new BusinessException(ExceptionEnum.CONFIRM_ORDER_ERROR);
        }

    }


    /**
     * 挑选座位
     * 1、前端没有选座，自动选择符合条件的座位
     * 2、前端选座，根据偏移值来遍历符合条件的座位
     * @param date 日期
     * @param trainCode 车次编号
     * @param seatType 座位类型
     * @param column 挑选的列号
     * @param offsetList 偏移值列表
     * @param startIndex 起始站序
     * @param endIndex 终点站序
     * @param seatTypeColSize 座位类型对应的列数
     * @param finalSeatList 最终的选座结果
     */
    private void selectSeat(Date date, String trainCode, String seatType, String column,
                            List<Integer> offsetList, Integer startIndex, Integer endIndex,
                            Integer seatTypeColSize, List<DailyTrainSeat> finalSeatList) {
        // 临时选座数据
        List<DailyTrainSeat> selectSeatList;
        // 根据座位类型拿到该车次 “座位类型” 的车厢列表
        List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
        LOG.info("共有【{}】个车厢符合【{}】", carriageList.size(), SeatTypeEnum.getEnumByCode(seatType).getDesc());
        for (DailyTrainCarriage dailyTrainCarriage : carriageList) {
            // 每遍历一个新车厢，就清空临时选座数据，防止座位超出车厢范围
            selectSeatList = new ArrayList<>();
            // 根据车厢序号查询该车厢的座位
            List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, dailyTrainCarriage.getIndex());
            LOG.info("车厢序号【{}】的座位数：{}", dailyTrainCarriage.getIndex(), seatList.size());
            // 遍历整个车厢的座位
            for (int i = 0; i < seatList.size(); i++) {
                DailyTrainSeat dailyTrainSeat = seatList.get(i);
                Integer seatIndex = dailyTrainSeat.getCarriageSeatIndex();
                String col = dailyTrainSeat.getCol();

                // 判断当前座位是否在已经选中到临时选座数据中，选中就退出本次循环到下一个座位
                boolean alreadyChoose = false;
                for (DailyTrainSeat finalSeat : finalSeatList) {
                    if (finalSeat.getId().equals(dailyTrainSeat.getId())) {
                        LOG.info("座位【{}】已选中", seatIndex);
                        alreadyChoose = true;
                        break;
                    }
                }
                if (alreadyChoose) continue;

                // 判断column列号，有值的话对比列号
                if (!StrUtil.isBlank(column) && !column.equals(col)) {
                    LOG.info("座位【{}】列号【{}】不匹配，目标列号【{}】", seatIndex, col, column);
                    continue;
                }

                // 选择第一个座位
                Boolean isChoose = seatSell(dailyTrainSeat, startIndex, endIndex);
                if (isChoose) {
                    // 选中座位，添加到临时选座数据
                    selectSeatList.add(dailyTrainSeat);
                } else {
                    // 有选座情况下：该座位没有被选中，从下一排对应的列号开始索引
                    if (!StrUtil.isBlank(column)) i += seatTypeColSize - 1;
                    continue;
                }

                // 是否全部选座成功
                boolean isGetAllOffsetSeat = true;

                // 根据offset偏移值选剩下的座位
                if (CollUtil.isNotEmpty(offsetList)) {
                    // 从索引1 开始，索引0 是当前已选中的票
                    for (int j = 1; j < offsetList.size(); j++) {
                        Integer offset = offsetList.get(j);
                        // 当前座位索引加上下一个座位的偏移值：得到下一个座位的索引
                        int nextIndex = i + offset;

                        // 有选座时，一定是在同一个车厢，判断下一个座位的索引是否超出当前车厢的索引范围
                        if (nextIndex >= seatList.size()) {
                            LOG.info("座位【{}】不可选，超出车厢范围", nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }
                        // 下一个座位选座
                        DailyTrainSeat nextSeat = seatList.get(nextIndex);
                        LOG.info("下一座偏移值：{}，座位【{}】列号【{}】", offset, nextIndex + 1, nextSeat.getCol());
                        Boolean isChooseNext = seatSell(nextSeat, startIndex, endIndex);
                        if (isChooseNext) {
                            // 选中座位，添加到临时选座数据
                            selectSeatList.add(nextSeat);
                        } else {
                            // 下一个座位没有被选中，那么第一个座位从下一排开始索引
                            i += seatTypeColSize - 1;
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }

                // 判断是否全部选座成功，没有就进行下一次循环，并清空选座数据
                if (!isGetAllOffsetSeat) {
                    selectSeatList = new ArrayList<>();
                    continue;
                }

                // 全部选座成功，保存临时选好的座位到最终选座数据
                finalSeatList.addAll(selectSeatList);
                return;
            }
        }

    }

    /**
     * 计算某座位在区间内是否可卖（起始站为 1）
     * 例：se11=0001,本次购买区间站1~4，则区间已售000
     * 全部是0，表示这个区间可买；只要有1，就表示区间内已售过票
     * <p>
     * 选中后，要计算购票后的se11,比如原来是0001，本次购买区间站1~4
     * 方案：构造本次购票造成的售卖信息1110，和原se11 0001按位与，最终得到1111
     */
    private Boolean seatSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {
        Integer seatIndex = dailyTrainSeat.getCarriageSeatIndex();
        String col = dailyTrainSeat.getCol();
        // 假设 0001，售卖 2 ~ 4
        String sell = dailyTrainSeat.getSell();
        // 截取为 0|00|1 为 00
        String sellPart = sell.substring(startIndex, endIndex);
        if (Integer.parseInt(sellPart) > 0) {
            LOG.info("座位【{}】列号【{}】在区间【{}】内已售票", seatIndex, col, startIndex + " - " + endIndex);
            return false;
        } else {
            LOG.info("座位【{}】列号【{}】在区间【{}】内未售票", seatIndex, col, startIndex + " - " + endIndex);
            // 替换后 11
            String curSell = sellPart.replace('0', '1');
            // 往前补0，补(到达站站序)的数量 011
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            // 往后补0，补sell的数量 0110
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            // 当前区间售票信息curSeLL与库里的已售信息sell按位与，即可得到该座位卖出此票后的售票详情，0111
            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            // 补0，保证和sell位数一致 0111
            newSell = StrUtil.fillBefore(newSell, '0', sell.length());

            dailyTrainSeat.setSell(newSell);
            LOG.info("座位【{}】被选中，原售票信息：{}，车站区间：{}，即：{}，最终售票信息：{}",
                    seatIndex, sell, startIndex + " - " + endIndex, curSell, newSell);
            return true;
        }
    }

    /**
     * 预扣减余票数量，并判断余票是否足够
     */
    private static boolean reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
        boolean isTicketEmpty = false;
        for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seatType = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatType) {
                case TDZ -> {
                    int tdzCount = dailyTrainTicket.getTdz() - 1;
                    if (tdzCount < 0) {
                        dailyTrainTicket.setTdz(tdzCount);
                        isTicketEmpty = true;
                    }
                }
                case YDZ -> {
                    int ydzCount = dailyTrainTicket.getYdz() - 1;
                    if (ydzCount < 0) {
                        dailyTrainTicket.setYdz(ydzCount);
                        isTicketEmpty = true;
                    }
                }
                case EDZ -> {
                    int edzCount = dailyTrainTicket.getEdz() - 1;
                    if (edzCount < 0) {
                        dailyTrainTicket.setEdz(edzCount);
                        isTicketEmpty = true;
                    }
                }
                case RW -> {
                    int rwCount = dailyTrainTicket.getRw() - 1;
                    if (rwCount < 0) {
                        dailyTrainTicket.setRw(rwCount);
                        isTicketEmpty = true;
                    }
                }
                case YW -> {
                    int ywCount = dailyTrainTicket.getYw() - 1;
                    if (ywCount < 0) {
                        dailyTrainTicket.setYw(ywCount);
                        isTicketEmpty = true;
                    }

                }
            }
        }
        return isTicketEmpty;
    }

    /**
     * 查询前面有几个人在排队
     * @param id
     */
    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
        ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        int result = switch (statusEnum) {
            case PENDING -> 0; // 排队0
            case SUCCESS -> -1; // 成功
            case FAILURE -> -2; // 失败
            case EMPTY -> -3; // 无票
            case CANCEL -> -4; // 取消
            case INIT -> 999; // 需要查表得到实际排队数量
        };

        if (result == 999) {
            // 排在第几位，下面的写法：where a=1 and (b=1 or c=1) 等价于 where (a=1 and b=1) or (a=1 and c=1)
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode());
            return Math.toIntExact(confirmOrderMapper.countByExample(confirmOrderExample));
        } else {
            return result;
        }
    }

    /**
     * 取消排队，只有I状态才能取消排队，所以按状态更新
     * @param id
     */
    public Integer cancel(Long id) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.createCriteria()
                .andIdEqualTo(id)
                .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
        ConfirmOrder confirmOrder = new ConfirmOrder();
        confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());
        return confirmOrderMapper.updateByExampleSelective(confirmOrder, confirmOrderExample);
    }

}
